#include "qe_log.h"
#include "qe_time.h"
#include "qe_assert.h"
#include "qe_memory.h"
#include "qe_driver.h"
#include "libfdt.h"



QELOG_DOMAIN("axi-qspi");



#define AXI_QSPI_IER_ALL_MASK			0x00003FFF
#define AXI_QSPI_TXFIFO_RESET_MASK      0x00000020
#define AXI_QSPI_RXFIFO_RESET_MASK      0x00000040
#define AXI_QSPI_ENABLE_MASK            0x00000002
#define AXI_QSPI_MASTER_MODE_MASK       0x00000004
#define AXI_QSPI_MANUAL_SS_MASK         0x00000080
#define AXI_QSPI_TRANS_INHIBIT_MASK     0x00000100
#define AXI_QSPI_TX_FULL_MASK           0x00000008
#define AXI_QSPI_RX_EMPTY_MASK          0x00000001

#define AXI_QSPI_INTR_TX_EMPTY			0x00000004


typedef struct
{
	volatile qe_u32 reserved0[7];
	volatile qe_u32 dgier;			/* 0x1C Device global interrupt enable register */
	volatile qe_u32 isr;			/* 0x20 Interrupt status register */
	volatile qe_u32 reserved1;
	volatile qe_u32 ier;			/* 0x28 Interrupt enable registe */
	volatile qe_u32 reserved2[13];
	volatile qe_u32 cr;				/* 0x60 Control Register */
	volatile qe_u32 sr;				/* 0x64 Status Register */
	volatile qe_u32 dtr;			/* 0x68 Data Transmit Register */
	volatile qe_u32 drr;			/* 0x6C Data Receive Register */
	volatile qe_u32 ssr;			/* 0x70 Slave Select Register */
	volatile qe_u32 tfo;			/* 0x74 Transmit FIFO Occupancy */
	volatile qe_u32 rfo;			/* 0x78 Receive FIFO Occupancy */
} axi_qspi_reg;

typedef struct {
	qe_spi_bus bus;
	qe_spi_dev dev;
	axi_qspi_reg *reg;
} axi_qspi_dev;

static qe_ret axi_qspi_configure(qe_spi_dev *spi,
	qe_spi_configure *cfg)
{
	return qe_ok;
}

/**
 * @brief Read status register
 * 
 * @note:
 *  - This function is for compiler optimization.
 */
static inline qe_u32 read_sr(axi_qspi_dev *spi)
{
	volatile qe_u32 *p = &spi->reg->sr;
	return *p;
}

static inline qe_u32 read_isr(axi_qspi_dev *spi)
{
	volatile qe_u32 *p = &spi->reg->isr;
	return *p;
}

static qe_bool wait_for_tx_done(axi_qspi_dev *spi, qe_uint timeout_us)
{
	int count = 0;
	qe_u32 isr;

	while (1) {
		isr = read_isr(spi);
		if (isr & AXI_QSPI_INTR_TX_EMPTY) {
			return qe_true;
		}
		qe_usleep(1);
		if (count++ >= timeout_us) {
			return qe_false;
		}
	}
}

static qe_ret axi_qspi_xfer_once(axi_qspi_dev *spi, qe_u8 *txbuf,
    qe_u8 *rxbuf, int once_len)
{
	qe_ret ret;
	qe_u8 *txp = txbuf;
	qe_u8 *rxp = rxbuf;
    qe_u8 rxdata = 0xff;
	qe_u8 txdata = 0xff;
	qe_u32 cr;
	qe_u32 isr;
	qe_u32 sr;
    qe_u32 txbytes = once_len;
    qe_u32 rxbytes = once_len;

	/* Reset fifo */
	spi->reg->cr |= AXI_QSPI_RXFIFO_RESET_MASK | AXI_QSPI_TXFIFO_RESET_MASK;

    while (txbytes > 0 || rxbytes > 0) {

		/* Write txfifo */
		while ((txbytes > 0) && !(spi->reg->sr & AXI_QSPI_TX_FULL_MASK)) {
			if (txbuf != QE_NULL) {
				spi->reg->dtr = *txp++;
			} else {
				spi->reg->dtr = txdata;
			}
			txbytes--;
		}

		/* Start transmit by setting the transmit inhibit bit to 0 */
		spi->reg->cr &= ~AXI_QSPI_TRANS_INHIBIT_MASK;

		/* Wait transfer done */
		// while (!(spi->reg->sr & AXI_QSPI_RX_EMPTY_MASK))
		// 	;
		do {
			isr = read_sr(spi);
		} while (!(isr & AXI_QSPI_INTR_TX_EMPTY));
		// if (!wait_for_tx_done(spi, 2000)) {
		// 	qe_error("wait tx done timeout");
		// 	spi->reg->cr |= AXI_QSPI_TRANS_INHIBIT_MASK;
		// 	return qe_err_common;
		// }

		/* Clear tx empty flag */
		spi->reg->isr |= AXI_QSPI_INTR_TX_EMPTY;

		/* Stop transmit by setting the transmit inhibit bit to 1 */
		spi->reg->cr |= AXI_QSPI_TRANS_INHIBIT_MASK;

		/* Read rxfifo */
		while ((rxbytes > 0) && !(spi->reg->sr & AXI_QSPI_RX_EMPTY_MASK)) {
			rxbytes--;
			rxdata = spi->reg->drr;
			if (rxbuf != QE_NULL) {
				*rxp++ = rxdata;
			}
		}
	}

    if (txbytes!=0 || rxbytes!=0) {
		ret = qe_err_common;
	}

    /* stop transmit, reset fifo */
	spi->reg->cr |= AXI_QSPI_RXFIFO_RESET_MASK | AXI_QSPI_TXFIFO_RESET_MASK | AXI_QSPI_TRANS_INHIBIT_MASK;

	return ret;
}

static qe_u32 axi_qspi_xfer(qe_spi_dev *spi,
	qe_spi_message *message)
{
    int n;
	qe_u8 *txbuff = (qe_u8 *)message->send_buf;
	qe_u8 *rxbuff = (qe_u8 *)message->recv_buf;
    qe_u32 rlen = message->length;
    qe_u32 proclen = 0;
	axi_qspi_dev *dev = (axi_qspi_dev *)spi->parent.priv;

	qe_assert(spi != QE_NULL);
	qe_assert(dev != QE_NULL);
    qe_assert(message != QE_NULL);

#if (CONFIG_AXI_QSPI_HEXDUMP)
	if (txbuff) {
		qe_debug("txbuf %d", rlen);
		qe_hexdump_debug(rxbuff, rlen);
	}
#endif

    /* cs take */
    if (message->cs_take) {
		//qe_debug("cs take");
		dev->reg->ssr = ~1;
    }

    do {
		n = ((message->length-proclen)>256) ? 256 : (message->length-proclen);
		axi_qspi_xfer_once(dev, txbuff+proclen, rxbuff+proclen, n);
		proclen += n;
	} while (proclen<message->length);

	/* cs release */
	if (message->cs_release) {
		dev->reg->ssr = ~0;
		//qe_debug("cs release");
	}

#if (CONFIG_AXI_QSPI_HEXDUMP)
	if (rxbuff) {
		qe_debug("rxbuf %d", rlen);
		qe_hexdump_debug(rxbuff, rlen);
	}
#endif

	return rlen;
}

static qe_spi_ops axi_qspi_ops = {
	.configure = axi_qspi_configure,
	.xfer      = axi_qspi_xfer
};

static void axi_qspi_hw_init(axi_qspi_dev *dev)
{
	/* disable global interrupt */
	dev->reg->dgier = 0;

    /* disable all interrupts */
	dev->reg->ier &= ~(AXI_QSPI_IER_ALL_MASK);

    /* reset tx/rx fifo, set master mode */
	dev->reg->cr |= AXI_QSPI_RXFIFO_RESET_MASK |
					AXI_QSPI_TXFIFO_RESET_MASK |
					AXI_QSPI_MANUAL_SS_MASK |
					AXI_QSPI_MASTER_MODE_MASK |
					AXI_QSPI_TRANS_INHIBIT_MASK |
					AXI_QSPI_ENABLE_MASK;

	while (dev->reg->cr & (AXI_QSPI_RXFIFO_RESET_MASK|AXI_QSPI_TXFIFO_RESET_MASK))
		;

    qe_debug("reset done");
}

static qe_ret axi_qspi_probe(const void *fdt, int offset)
{
	int len;
	const char *name;
	const char *dev_name;
	const qe_u32 *prop_reg;
	axi_qspi_dev *dev;

    name = fdt_get_name(fdt, offset, QE_NULL);

    prop_reg = (const qe_u32 *)fdt_getprop(fdt, offset, "reg", &len);
    if (!prop_reg) {
        qe_error("%s no reg prop", name);
        return qe_err_param;
    }

	dev_name = fdt_getprop(fdt, offset, "dev", &len);
    if (!dev_name) {
        qe_error("%s no dev prop", name);
        return qe_err_param;
    }

    dev = qe_malloc(sizeof(axi_qspi_dev));
    qe_assert(dev);

	dev->reg = (axi_qspi_reg *)(qe_ubase)fdt32_to_cpu(*prop_reg);

	/* do hardware spi init */
	axi_qspi_hw_init(dev);

	/* register spi bus */
	qe_spi_bus_register(&dev->bus, name, &axi_qspi_ops);
	qe_debug("register spi bus %s", name);

	/* attach spi device to spi bus */
	qe_spi_bus_attach_device(&dev->dev, dev_name, name, dev);
	qe_debug("register spi dev %s", dev_name);

	return qe_ok;
}

static const qe_device_id axi_qspi_ids[] = {
    {.compatible = "xlnx,axi-qspi",},
    {}
};

QE_DRIVER(axi_qspi) = {
    .name = "axi_qspi",
    .of_match = axi_qspi_ids,
    .probe = axi_qspi_probe,
};

QE_DRIVER_FORCE_EXPORT(axi_qspi);